Skip to content

Allow inter-parameter dependencies #2079

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 14, 2017
Merged

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Mar 12, 2017

Allow a later parameter's type to refer to an earlier parameter in the same parameter section. Previously, we allowed only references to parameters in preceding sections. This is simpler, but it
can force unnatural currying. The problem becomes urgent only if we want to add dependent types more broadly. It feels natural to write

def f(n: Int, v: Vec(n))

and being forced to curry this as def fun(n: Int)(v: Vec(n)) is annoying.

The changes in this PR have given me an idea for a much more sweeping change. We might be able to unify MethodTypes and PolyTypes, which would improve regularity of the language quite significantly and which would open up lots of new possibilities. But one thing after another.

Review by @gsps?

@odersky
Copy link
Contributor Author

odersky commented Mar 12, 2017

This PR includes in its first and last commit the commits of #2078. I wanted to add it so we could have the interesting test case suggested by @smarter.

@smarter
Copy link
Member

smarter commented Mar 12, 2017

#2078 merged, you should be able to rebase.

@@ -233,6 +233,12 @@ object ProtoTypes {
typer.adapt(targ, formal, arg)
}

/** The type of the argument `arg`.
* @pre `arg` ahs been typed before
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: ahs -> has

addArg(typedArg(arg, formal), formal)
if (methodType.isParamDependent)
_.substParam(MethodParam(methodType, n), typeOfArg(arg))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible performance concern: The resulting type transformer is applied below using formals1.mapconserve(substParam), so we do O(|formals|^2) substitutions.

It might be worth collecting just the substitution pairs, by returning Option[(MethodParam, Type)] instead, and whenever we finalize an argument i, we apply all of them using formal_i.substParam(listOfMts, listOfTps). This would require adding a substituter that can handle multiple ParamTypes at once (cf. Type.substParam).

Moreover, when adding arg#i we could avoid mapping over the first i parameters in formals1, since only parameters j > i can depend on i.

Copy link
Contributor Author

@odersky odersky Mar 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not convinced it will be a problem. First, how many methods will be paramDependent? Then, how long is the typical parameter list? If it is 2, then the code given is the best (just one substitution). If it is 3 or 4, not much worse. Also, note that single parameter substitutions are faster than multiple parameter ones which require a linear search anyway. So, in summary, let's wait until the profiler indicates this is a hotspot.

@@ -278,7 +278,8 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table, posUnpickle
result
case METHODtype =>
val (names, paramReader) = readNamesSkipParams
val result = MethodType(names.map(_.toTermName), paramReader.readParamTypes[Type](end))(
val result = MethodType(names.map(_.toTermName))(
mt => paramReader.readParamTypes[Type](end), // !!!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That "!!!" is slightly alarming :-)

I suppose you are referring to the fact that we now rely on the constructor calling the two closures in the right order, which is somewhat risky indeed. Why not read them explicitly and construct a constant function?

/** Check that method parameter types do not reference their own parameter
* or later parameters in the same parameter section.
*/
def checkNoForwardDependencies(vparams: List[ValDef])(implicit ctx: Context): Unit = vparams match {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure there's a good reason, but it seems that here we are duplicating the checking provided by Config.checkMethodTypes. Why do we need both?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One is a source check, the other an internal assertion that results in an exception. Not all method types are created directly from source, so the assertion is useful.

def f(x: C, y: x.T): x.T = y // ok

val c = new C { type T = String }
val c2 = c
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we only added val c2 to see what type is inferred, so we can now remove it, no? A similar thing goes for the val d below, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but it's a test case. Does not really matter either way.

@gsps
Copy link
Contributor

gsps commented Mar 13, 2017

(I unfortunately put one of my comments in the outdated version of TreeUnpickler.scala, so the comment is collapsed by default. I think the point still stands, please have a look.)

odersky added 9 commits March 14, 2017 12:05
To allow for dependencies between method type parameters, construct MethodTypes
from a closure that maps the currently constructed MethodType to its parameter types.
Also: check validity of method types, so that no
forward references occur.
Take parameter dependencies into account when typechecking arguments.
The dropped method takes direct parameter types but a result type expression.
Since parameter types are now in general dependent as well, that method is
mostly redundant.
All PolyTypes get variances passed, so isTypeLambda is always true
and the deleted assert is never triggered.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants